package com.fxl.majiang.room;

import com.alibaba.fastjson.JSON;
import com.fxl.majiang.enums.*;
import com.fxl.majiang.exception.CustomException;
import com.fxl.majiang.message.MessageHandler;
import com.fxl.majiang.message.output.*;
import lombok.SneakyThrows;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author fengxinglei
 * @date 2022/8/3 13:58
 * @description 玩家管理
 */
public class PlayerHandler {

    // 操作等待时长
    private static final long WAITING_FOR_MILLIS = 10000L;

    @SneakyThrows
    public synchronized static void ready(String userId) {
        // 获取用户所在房间
        Room room = RoomManager.getUserRoom(userId);
        if (room == null) {
            throw new CustomException("您未加入任何房间");
        }
        Player player = room.getPlayer(userId);
        if (player.getPlayStatus() == PlayerStatusEnum.READY) {
            throw new CustomException("已准备，请勿重复操作");
        }
        // 加入到准备队列
        player.setPlayStatus(PlayerStatusEnum.READY);
        List<Player> allPlayers = room.getAllPlayers();
        List<String> allUserIds = allPlayers.stream()
                .map(Player::getUserId).collect(Collectors.toList());

        // 如果大家都准备了，则开始游戏
        if (room.isAllReady()) {
            room.start();
            // 每个用户手牌数量
            Map<String, Integer> userTilesCount = new HashMap<>();
            allPlayers.stream().forEach(_player -> {
                userTilesCount.put(_player.getUserId(), _player.getOwnedTiles().size());
            });
            // 第一个可以补花的人
            String firstUserIdSupplementWhenStart = room.firstSupplementWhenStart();
            String bankerUserId = room.getBankerUserId();
            List<TilesEnum> versatileTiles = room.getDealers().getVersatileTiles();
            // 给每个玩家推送自己的手牌,以及其他用户的手牌数量
            allPlayers.stream().forEach(_player -> {
                String thisUserId = _player.getUserId();
                List<TilesEnum> ownedTiles = _player.getOwnedTiles();
                OutputStartTilesMsgData data = new OutputStartTilesMsgData();
                data.setOwnedTiles(ownedTiles);
                data.setVersatileTiles(versatileTiles);
                for (String key : userTilesCount.keySet()) {
                    if (!key.equals(thisUserId)) {
                        data.addOtherCount(key, userTilesCount.get(key));
                    }
                }
                data.setFirstSupplementUserId(firstUserIdSupplementWhenStart);
                // 如果是没有第一个补花的人，则庄家先出牌
                if (firstUserIdSupplementWhenStart == null && bankerUserId.equals(thisUserId)) {
                    data.setAnGangTiles(_player.getAnGangTilesAfterGrabbing());
                    data.setCanZiMo(RuleManager.canZiMo(_player, room.getDealers().getVersatileTiles()));
                }
                data.setBankerUserId(bankerUserId);
                OutputMessage outputMessage = new OutputMessage(OutputMsgTypeEnum.START_TILES.getCode(), data);
                MessageHandler.sendToUser(thisUserId, outputMessage);
            });
            sendLeftTilesCountMsg(room);
        } else {
            // 广播，用户已准备
            OutputUserStatusMsgData msgData = new OutputUserStatusMsgData(userId, PlayerStatusEnum.READY.getCode());
            OutputMessage outputMessage = new OutputMessage(OutputMsgTypeEnum.USER_STATUS.getCode(), JSON.toJSONString(msgData));
            MessageHandler.sendToUsers(allUserIds, outputMessage);
        }

    }


    @SneakyThrows
    public synchronized static void unReady(String userId) {
        Room room = RoomManager.getUserRoom(userId);
        if (room == null) {
            throw new CustomException("您未加入任何房间");
        }
        Player player = room.getPlayer(userId);
        if (player.getPlayStatus() == PlayerStatusEnum.UN_READY) {
            throw new CustomException("当前状态即为未准备，请勿重复操作");
        }
        // 加入到准备队列
        player.setPlayStatus(PlayerStatusEnum.UN_READY);
        // 广播，用户未准备
        List<String> allUserIds = room.getAllPlayers().stream()
                .map(Player::getUserId).collect(Collectors.toList());
        OutputUserStatusMsgData msgData = new OutputUserStatusMsgData(userId, PlayerStatusEnum.UN_READY.getCode());
        OutputMessage outputMessage = new OutputMessage(OutputMsgTypeEnum.USER_STATUS.getCode(), JSON.toJSONString(msgData));
        MessageHandler.sendToUsers(allUserIds, outputMessage);
    }

    /**
     * 用户出牌
     * @param userId
     * @param tile
     */
    @SneakyThrows
    public synchronized static void playTheCard(String userId, TilesEnum tile) {
        if (tile == null) {
            throw new CustomException("请选择正确的手牌");
        }
        CardTypeEnum type = tile.getType();
        if (type == CardTypeEnum.HEI_HUA || type == CardTypeEnum.HONG_HUA || type == CardTypeEnum.BAI_BAN) {
            throw new CustomException("不能出花牌");
        }
        Room room = RoomManager.getUserRoom(userId);
        if(!room.isCurPlayer(userId)) {
            throw new CustomException("别着急，其他玩家正在思考");
        }
        if (room.isSupplementWhenStart()) {
            throw new CustomException("正在初始补花中，无法出牌");
        }
        Player player = room.getPlayer(userId);
        if (player == null) {
            throw new CustomException("您不在当前房间");
        }
        Dealers dealers = room.getDealers();
        if (dealers.isVersatile(tile)) {
            throw new CustomException("不能出百搭牌");
        }
        player.playTheCard(tile);
        // 设置桌子上最新的牌
        room.setLastTile(tile);
        // 将自己设置为上一个玩家
        room.setPreviousUserId(userId);
        // 下一个用户
        String nextUserId = room.nextPlayer();
        List<Player> allPlayers = room.getAllPlayers();
        // 下家是否可以抓牌，如果有玩家有吃碰胡等操作，则下家先不能抓牌，待操作完毕后看结果
        boolean canNextGrabbing = true;
        boolean canHu = false;
        Map<String , OutputPlayedCardMsgData> msgDataMap = new HashMap<>();
        // 通知所有用户，包括用户是否能吃、碰、杠、胡这张牌
        for (Player _player: allPlayers) {
            OutputPlayedCardMsgData data = new OutputPlayedCardMsgData(userId, tile);
            data.setNextUserId(nextUserId);
            // 只计算其他人的吃碰胡，不用计算出牌人自己的
            if(!_player.getUserId().equals(userId)) {
                // 判断下家是否能吃
                boolean canChiIt = false;
                if (_player.getUserId().equals(nextUserId)) {
                    List<List<TilesEnum>> chiItGroups = RuleManager.getCanChiItGroups(_player, tile, dealers.getVersatileTiles());
                    canChiIt = !CollectionUtils.isEmpty(chiItGroups);
                    data.setCanChiIt(canChiIt);
                    data.setChiItGroups(chiItGroups);
                    if (canChiIt) {
                        room.addToCanOperatePlayedCardMap(_player.getUserId(), OperationOrderEnum.CHI);
                    }
                }
                // 玩家是否可以碰这张牌
                boolean canPengIt = RuleManager.canPengIt(_player, tile);
                data.setCanPengIt(canPengIt);
                if (canPengIt) {
                    room.addToCanOperatePlayedCardMap(_player.getUserId(), OperationOrderEnum.PENG);
                }
                // 玩家是否可以明杠这张牌
                boolean canMingGangIt = RuleManager.canMingGangIt(_player, tile);
                data.setCanMingGangIt(canMingGangIt);
                if (canMingGangIt) {
                    room.addToCanOperatePlayedCardMap(_player.getUserId(), OperationOrderEnum.MING_GANG_IT);
                }
                // 玩家是否可以点炮胡这张牌
                boolean canDianPaoIt = RuleManager.canDianPaoIt(_player, tile);
                data.setCanDianPaoIt(canDianPaoIt);
                if (canDianPaoIt) {
                    canHu = true;
                    room.addToCanOperatePlayedCardMap(_player.getUserId(), OperationOrderEnum.DIAN_PAO);
                }
                // 设置下家是否可以抓牌，如果有玩家有吃碰胡等操作，则下家先不能抓牌，待操作完毕后看结果
                if (canNextGrabbing) {
                    canNextGrabbing = (!canChiIt && !canPengIt && !canMingGangIt && !canDianPaoIt);
                }
            }
            msgDataMap.put(_player.getUserId(), data);
        }
        // 是否出完了,并且没人能胡，则结束本局游戏
        if (!canHu && CollectionUtils.isEmpty(dealers.getRemainingTiles())) {
            handleAfterNoRemainingTiles(room);
        } else {
            for (String uid: msgDataMap.keySet()) {
                OutputPlayedCardMsgData data = msgDataMap.get(uid);
                data.setNextUserId(nextUserId);
                data.setCanNextGrabbing(canNextGrabbing);
                OutputMessage outputMessage = new OutputMessage(OutputMsgTypeEnum.PLAYED_CARD.getCode(), data);
                MessageHandler.sendToUser(uid, outputMessage);
            }
        }
    }

    /**
     * 用户放弃操作，比如吃、碰、胡
     * @param userId
     */
    public synchronized static void giveUpOperation(String userId) {
        Room userRoom = RoomManager.getUserRoom(userId);
        userRoom.selectedOperatePlayedCard(userId, OperationOrderEnum.GIVE_UP_OPERATION);
        // 如果都已经放弃了选择，则给下家抓牌
        if (userRoom.hasFinishOperatePlayedCard()) {
            grabbing(userRoom.getPlayingUserId());
            userRoom.clearOperatePlayedCardMap();
        }
    }

    /**
     * 抓牌
     * @return
     */
    @SneakyThrows
    public synchronized static TilesEnum grabbing(String userId) {
        Room room = RoomManager.getUserRoom(userId);
        if (!room.isCurPlayer(userId)) {
            throw new CustomException("别着急，还没轮到你");
        }
        // 获取玩家信息
        Player player = room.getPlayer(userId);
        Dealers dealers = room.getDealers();
        // 随机获取一张牌
        TilesEnum tile = dealers.getSingle();
        player.grabbing(tile);
        OutputSelfGrabbingMsgData selfMsgData = new OutputSelfGrabbingMsgData(player.getOwnedTiles(), tile);
        selfMsgData.setMingGangTiles(player.getMingGangTilesAfterGrabbing());
        selfMsgData.setAnGangTiles(player.getAnGangTilesAfterGrabbing());
        selfMsgData.setCanZiMo(RuleManager.canZiMo(player, dealers.getVersatileTiles()));
        // 通知抓牌用户抓到的牌是什么
        MessageHandler.sendToUser(userId, new OutputMessage(OutputMsgTypeEnum.SELF_GRABBING.getCode(), selfMsgData));
        List<String> otherUserIds = new ArrayList<>();
        // 通知其他用户该用户在补花
        room.getAllPlayers().stream().forEach(_player -> {
            if (!_player.getUserId().equals(userId)) {
                otherUserIds.add(_player.getUserId());
            }
        });
        MessageHandler.sendToUsers(otherUserIds, new OutputMessage(OutputMsgTypeEnum.OTHER_GRABBING.getCode(),  new OutputOtherGrabbingMsgData(userId)));
        sendLeftTilesCountMsg(room);
        return tile;
    }

    /**
     * 起手补花
     * @param userId
     * @return
     */
    @SneakyThrows
    public synchronized static void supplementWhenStart(String userId, List<TilesEnum> tiles) {
        // 获取房间
        Room room = RoomManager.getUserRoom(userId);
        if (!room.isCurPlayer(userId)) {
            throw new CustomException("还没轮到你");
        }
        // 开始补花
        supplement(userId, tiles,true);
        String nextUserId = room.nextSupplementWhenStart(userId);
        if (StringUtils.hasText(nextUserId)) {
            room.setPlayingUserId(nextUserId);
            OutputCanSupplementReminderMsgData data = new OutputCanSupplementReminderMsgData(nextUserId);
            MessageHandler.sendToUsers(room.getAllUserIds(), new OutputMessage(OutputMsgTypeEnum.SUPPLEMENT_WHEN_START_REMINDER.getCode(), data));
            room.setSupplementWhenStart(true);
        }
        // 如果没有找到，则让庄家出牌
        else {
            String bankerUserId = room.getBankerUserId();
            room.setPlayingUserId(bankerUserId);
            OutputPlayReminderMsgData data = new OutputPlayReminderMsgData();
            data.setUserId(bankerUserId);
            Player player = room.getPlayer(bankerUserId);
            data.setMingGangTiles(player.getMingGangTilesAfterGrabbing());
            data.setAnGangTiles(player.getAnGangTilesAfterGrabbing());
            data.setCanZiMo(RuleManager.canZiMo(player, room.getDealers().getVersatileTiles()));
            MessageHandler.sendToUsers(room.getAllUserIds(), new OutputMessage(OutputMsgTypeEnum.PLAY_REMINDER.getCode(), data));
            room.setSupplementWhenStart(false);
        }
        sendLeftTilesCountMsg(room);
    }

    /**
     * 给所有用户推送剩余手牌
     * @param room
     */
    private synchronized static void sendLeftTilesCountMsg(Room room) {
        OutputLeftTilesCountMsgData data = new OutputLeftTilesCountMsgData(room.getDealers().getRemainingTiles().size());
        MessageHandler.sendToUsers(room.getAllUserIds(), new OutputMessage(OutputMsgTypeEnum.LEFT_TILES_COUNT.getCode(), data));
    }

    /**
     * 补花
     * @param userId
     * @return
     */
    @SneakyThrows
    public synchronized static void supplement(String userId, List<TilesEnum> tiles, boolean isSupplementWhenStart) {
        // 判断这张牌是否可以补花
        if (RuleManager.canSupplementAll(tiles)) {
            // 获取房间
            Room room = RoomManager.getUserRoom(userId);
            Dealers dealers = room.getDealers();
            // 判断有没有花牌是百搭的，如果有，则不能补
            for (TilesEnum tile: tiles) {
                if (dealers.isVersatile(tile)) {
                    throw new CustomException("该牌为百搭牌，不能补");
                }
            }
            // 获取用户信息
            Player player = room.getPlayer(userId);
            // 判断是否拥有这张手牌
            if (player.hasOwnedTiles(tiles)) {
                List<TilesEnum> grabbingList = new ArrayList<>();
                for (TilesEnum tile : tiles) {
                    // 移除这张手牌
                    player.removeOwnedTile(tile);
                    // 将这张牌加进花牌牌组
                    player.addOwnedHuaTilesGroup(tile);
                    // 重新获取一张牌
                    TilesEnum single = dealers.getSingle();
                    player.grabbing(single);
                    grabbingList.add(single);
                }
                // 通知抓牌用户抓到的牌是什么
                MessageHandler.sendToUser(userId, new OutputMessage(OutputMsgTypeEnum.SELF_SUPPLEMENT.getCode(),  new OutputSelfSupplementMsgData(grabbingList, player, isSupplementWhenStart)));
                List<String> otherUserIds = new ArrayList<>();
                // 通知其他用户该用户在补花
                room.getAllPlayers().stream().forEach(_player -> {
                    if (!_player.getUserId().equals(userId)) {
                        otherUserIds.add(_player.getUserId());
                    }
                });
                MessageHandler.sendToUsers(otherUserIds, new OutputMessage(OutputMsgTypeEnum.OTHER_SUPPLEMENT.getCode(),  new OutputOtherSupplementMsgData(player.getOwnedHuaTilesGroup(), userId)));
                sendLeftTilesCountMsg(room);
            } else {
                throw new CustomException("你没有这些牌，请勿当老六");
            }
        } else {
            throw new CustomException("这些牌不能补花，请勿当老六");
        }
    }

    /**
     * 吃牌
     * @param userId
     * @param it - 被吃的那张牌 .比如 二万
     * @param selectedGroup - 选择用来吃它的牌组， 比如一万和三万
     */
    @SneakyThrows
    public static void chi(String userId, TilesEnum it, List<TilesEnum> selectedGroup) {
        if (selectedGroup.size() != 2) {
            throw new CustomException("牌型不对，请勿当老六");
        }
        // 获取房间
        Room room = RoomManager.getUserRoom(userId);
        if (!room.isCurPlayer(userId)) {
            throw new CustomException("别着急，还没轮到你");
        }
        // 判断这张牌是否最近打出的
        if (!room.isLastTile(it)) {
            throw new CustomException("这张牌不是最近打出那张牌，请勿当老六");
        }
        // 判断是否处于可吃牌操作队列中
        if (!room.checkCanOperatePlayedCard(userId, OperationOrderEnum.CHI)) {
            throw new CustomException("你不能吃这张牌，别当老六");
        }

        Player player = room.getPlayer(userId);
        // 判断是否可以吃这张牌
        if (RuleManager.canChiIt(player, it, selectedGroup, room.getDealers().getVersatileTiles())) {
            // 用户选择了吃操作  加上同步锁，避免一个人重复点击多次
            if (!room.selectedOperatePlayedCard(userId, OperationOrderEnum.CHI)) {
                throw new CustomException("你已经选择了操作，请勿重复操作");
            }
            // 这里用异步线程，是为了防止方法 synchronized 锁掉以后其他用户操作不了了
            long startMillis = System.currentTimeMillis();
            // 判断自己是否是操作竞争获胜者
            while (true) {
                // 判断是否所有人都已经结束选择，如果结束选择，则结束循环，继续下面的操作
                //TODO || System.currentTimeMillis() - startMillis >= WAITING_FOR_MILLIS 如果有倒计时，需要补上，避免条件出错，死循环
                if (room.hasFinishOperatePlayedCard() ) {
                    break;
                }
            }
            // 如果自己是胜者，继续下面的操作, 否则直接返回
            if (!room.isWinnerOfOperate(userId)) {
                return;
            }
            // 吃牌
            List<TilesEnum> chiGroup = player.chi(it, selectedGroup);
            // 从手牌中移除这两张牌
            player.removeOwnedTiles(selectedGroup);
            // 清除最近在桌上的牌
            room.clearLastTile();
            // 清除操作队列数据
            room.clearOperatePlayedCardMap();
            // 将上一位玩家刚打的手牌删除，因为已经到了吃牌玩家的手里
            Player previousPlayer = room.previousPlayer();
            previousPlayer.removeRecentPlayedTiles();
            // 通知所有用户吃牌完毕
            OutputMessage msg = new OutputMessage(OutputMsgTypeEnum.CHI.getCode(), new OutputChiMsgData(userId,previousPlayer.getUserId(), chiGroup, selectedGroup));
            MessageHandler.sendToUsers(room.getAllUserIds(), msg);
        } else {
            throw new CustomException("你不能吃这张牌");
        }
    }

    /**
     * 碰这张牌
     * @param userId
     * @param it
     */
    @SneakyThrows
    public static void peng(String userId, TilesEnum it) {
        // 获取房间
        Room room = RoomManager.getUserRoom(userId);
        // 判断这张牌是否最近打出的
        if (!room.isLastTile(it)) {
            throw new CustomException("这张牌不是最近打出那张牌，请勿当老六");
        }
        // 判断是否处于可碰牌操作队列中
        if (!room.checkCanOperatePlayedCard(userId, OperationOrderEnum.PENG)) {
            throw new CustomException("你不能碰这张牌，别当老六");
        }
        Player player = room.getPlayer(userId);
        // 判断是否可以吃这张牌
        if (RuleManager.canPengIt(player, it)) {
            // 用户选择了碰操作  加上同步锁，避免一个人重复点击多次
            if (!room.selectedOperatePlayedCard(userId, OperationOrderEnum.PENG)) {
                throw new CustomException("你已经选择了操作，请勿重复操作");
            }
            long startMillis = System.currentTimeMillis();
            // 判断自己是否是操作竞争获胜者
            while (true) {
                // 判断是否所有人都已经结束选择，如果结束选择，则结束循环，继续下面的操作
                // TODO  || System.currentTimeMillis() - startMillis >= WAITING_FOR_MILLIS
                if (room.hasFinishOperatePlayedCard()) {
                    break;
                }
            }
            // 如果自己是胜者，继续下面的操作, 否则直接返回
            if (!room.isWinnerOfOperate(userId)) {
                return;
            }
            // 碰牌
            player.peng(it);
            // 从手牌中移除这两张牌
            player.removeOwnedTiles(it, 2);
            // 清除最近在桌上的牌
            room.clearLastTile();
            // 清除操作队列数据
            room.clearOperatePlayedCardMap();
            // 将上一位玩家刚打的手牌删除，因为已经到了吃牌玩家的手里
            Player previousPlayer = room.previousPlayer();
            previousPlayer.removeRecentPlayedTiles();
            // 设置出牌人
            room.setPlayingUserId(player.getUserId());
            // 通知所有用户碰牌完毕
            OutputMessage msg = new OutputMessage(OutputMsgTypeEnum.PENG.getCode(), new OutputPengMsgData(userId, it, room.getPreviousUserId()));
            MessageHandler.sendToUsers(room.getAllUserIds(), msg);
        } else {
            throw new CustomException("你不能碰这张牌，你的老六");
        }
    }

    /**
     * 明杠
     * @param userId
     * @return
     */
    @SneakyThrows
    public static void mingGangIt(String userId, TilesEnum tile) {
        // 获取房间
        Room room = RoomManager.getUserRoom(userId);
        // 判断是否处于可碰牌操作队列中
        if (!room.checkCanOperatePlayedCard(userId, OperationOrderEnum.MING_GANG_IT)) {
            throw new CustomException("你不能暗杠这张牌，别当老六");
        }
        // 获取用户信息
        Player player = room.getPlayer(userId);
        // 判断是否可以明杠
        if (RuleManager.canMingGangIt(player, tile)) {
            // 用户选择了碰操作  加上同步锁，避免一个人重复点击多次
            if (!room.selectedOperatePlayedCard(userId, OperationOrderEnum.MING_GANG_IT)) {
                throw new CustomException("你已经选择了操作，请勿重复操作");
            }
            long startMillis = System.currentTimeMillis();
            // 判断自己是否是操作竞争获胜者
            while (true) {
                // 判断是否所有人都已经结束选择，如果结束选择，则结束循环，继续下面的操作
                // TODO  || System.currentTimeMillis() - startMillis >= WAITING_FOR_MILLIS
                if (room.hasFinishOperatePlayedCard()) {
                    break;
                }
            }
            // 如果自己是胜者，继续下面的操作, 否则直接返回
            if (!room.isWinnerOfOperate(userId)) {
                return;
            }
            // 加入用户明杠牌组
            player.mingGangIt(tile);
            // 将这张牌全部移除出手牌
            player.removeOwnedAllThisTile(tile);
            // 清除操作队列数据
            room.clearOperatePlayedCardMap();
            // 判断是否是最近一手别人打的
            if (room.isLastTile(tile)) {
                // 将上一位玩家刚打的手牌删除，因为已经到了吃牌玩家的手里
                Player previousPlayer = room.previousPlayer();
                previousPlayer.removeRecentPlayedTiles();
                // 清除最近在桌上的牌
                room.clearLastTile();
            }
            // 设置出牌人
            room.setPlayingUserId(player.getUserId());
            // 通知所有用户碰牌完毕
            OutputMessage msg = new OutputMessage(OutputMsgTypeEnum.MING_GANG_IT.getCode(), new OutputMingGangItMsgData(userId,tile, room.getPreviousUserId()));
            MessageHandler.sendToUsers(room.getAllUserIds(), msg);
        } else {
            throw new CustomException("你不能明杠这张牌");
        }
    }

    /**
     * 抓到某张牌之后明杠
     * @param userId
     * @return
     */
    @SneakyThrows
    public static void mingGangItAfterGrabbing(String userId, TilesEnum tile) {
        // 获取房间
        Room room = RoomManager.getUserRoom(userId);
        if (!room.isCurPlayer(userId)) {
            throw new CustomException("别着急，还没轮到你");
        }
        // 获取用户信息
        Player player = room.getPlayer(userId);
        // 判断是否可以明杠
        if (RuleManager.canMingGangItAfterGrabbing(player, tile)) {
            // 用户选择了碰操作  加上同步锁，避免一个人重复点击多次
            if (!room.selectedOperatePlayedCard(userId, OperationOrderEnum.MING_GANG_IT)) {
                throw new CustomException("你已经选择了操作，请勿重复操作");
            }
            // 加入用户明杠牌组
            player.mingGangItAfterGrabbing(tile);
            // 将这张牌全部移除出手牌
            player.removeOwnedTile(tile);
            // 清除操作队列数据
            room.clearOperatePlayedCardMap();
            // 通知自己
            OutputMingGangAfterGrabbingMsgData selfMsgData = new OutputMingGangAfterGrabbingMsgData();
            selfMsgData.setUserId(userId);
            selfMsgData.setTile(tile);
            selfMsgData.setOwnedGroups(player.getOwnedTilesGroup());
            OutputMessage selfMsg = new OutputMessage(OutputMsgTypeEnum.MING_GANG_AFTER_GRABBING.getCode(), selfMsgData);
            MessageHandler.sendToUser(userId, selfMsg);

            // 通知其他用户杠牌
            OutputMingGangAfterGrabbingMsgData otherMsgData = new OutputMingGangAfterGrabbingMsgData();
            otherMsgData.setUserId(userId);
            otherMsgData.setTile(tile);
            List<TilesGroup> ownedGroupInMsg = new ArrayList<>();
            player.getOwnedTilesGroup().forEach(group -> {
                // 对暗杠做隐藏处理
                if (group.getType()==TilesGroupTypeEnum.AN_GANG) {
                    TilesGroup tempGroup = new TilesGroup();
                    tempGroup.setType(TilesGroupTypeEnum.AN_GANG);
                    ownedGroupInMsg.add(tempGroup);
                } else {
                    ownedGroupInMsg.add(group);
                }
            });
            otherMsgData.setOwnedGroups(ownedGroupInMsg);
            OutputMessage otherMsg = new OutputMessage(OutputMsgTypeEnum.MING_GANG_AFTER_GRABBING.getCode(), otherMsgData);
            MessageHandler.sendToUsers(room.getAllUserIds().stream().filter(uid -> !uid.equals(userId)).collect(Collectors.toList()), otherMsg);
        } else {
            throw new CustomException("你不能明杠这张牌");
        }
    }

    /**
     * 暗杠
     * @param userId
     * @return
     */
    @SneakyThrows
    public static synchronized void anGang(String userId, TilesEnum tile) {
        // 获取房间
        Room room = RoomManager.getUserRoom(userId);
        if (!room.isCurPlayer(userId)) {
            throw new CustomException("别着急，还没轮到你");
        }
        // 获取用户信息
        Player player = room.getPlayer(userId);
        // 判断是否可以明杠
        if (RuleManager.canAnGang(player, tile)) {
            // 加入用户明杠牌组
            player.anGang(tile);
            // 移除手上这四张牌
            for (int i = 0 ; i<4; i++) {
                player.removeOwnedTile(tile);
            }
            // 通知用户到的牌是什么
            MessageHandler.sendToUser(userId, new OutputMessage(OutputMsgTypeEnum.SELF_AN_GANG.getCode(),  new OutputSelfAnGangMsgData(tile)));
            List<String> otherUserIds = new ArrayList<>();
            // 通知其他用户该用户在补花
            room.getAllPlayers().stream().forEach(_player -> {
                if (!_player.getUserId().equals(userId)) {
                    otherUserIds.add(_player.getUserId());
                }
            });
            MessageHandler.sendToUsers(otherUserIds, new OutputMessage(OutputMsgTypeEnum.OTHER_AN_GANG.getCode(),  new OutputOtherAnGangMsgData(userId)));
        } else {
            throw new CustomException("你不能暗杠这张牌");
        }
    }

    /**
     * 花暗杠
     * @param userId
     * @return
     */
    @SneakyThrows
    public static synchronized void huaAnGang(String userId, CardTypeEnum type) {
        // 获取房间
        Room room = RoomManager.getUserRoom(userId);
        if (!room.isCurPlayer(userId)) {
            throw new CustomException("别着急，还没轮到你");
        }
        // 获取用户信息
        Player player = room.getPlayer(userId);
        // 判断是否可以花暗杠
        if (RuleManager.canHuaAnGang(player, type)) {
            // 加入用户明杠牌组
            player.huaAnGang(type);
            // 如果是花牌，则移除所有该花色的花牌
            List<TilesEnum> tiles = TilesEnum.getByType(type);
            player.removeOwnedTiles(tiles);
            // 用户重新抓取一张手牌
            player.grabbing(room.getDealers().getSingle());
            // 通知用户到的牌是什么
            MessageHandler.sendToUser(userId, new OutputMessage(OutputMsgTypeEnum.SELF_HUA_AN_GANG.getCode(),  new OutputSelfHuaAnGangMsgData(tiles)));
            List<String> otherUserIds = new ArrayList<>();
            // 通知其他用户该用户在补花
            room.getAllPlayers().stream().forEach(_player -> {
                if (!_player.getUserId().equals(userId)) {
                    otherUserIds.add(_player.getUserId());
                }
            });
            MessageHandler.sendToUsers(otherUserIds, new OutputMessage(OutputMsgTypeEnum.OTHER_HUA_AN_GANG.getCode(),  new OutputOtherHuaAnGangMsgData(userId)));
        } else {
            throw new CustomException("你不能花暗杠，你的、个老六");
        }
    }

    @SneakyThrows
    public static void dianPao(String userId, TilesEnum it) {
        // 获取房间
        Room room = RoomManager.getUserRoom(userId);
        if (!room.isLastTile(it)) {
            throw new CustomException("这张牌不是最近打出的");
        }
        // 判断是否处于可吃牌操作队列中
        if (!room.checkCanOperatePlayedCard(userId, OperationOrderEnum.DIAN_PAO)) {
            throw new CustomException("你不能点炮这张牌，别当老六");
        }
        // 获取用户信息
        Player player = room.getPlayer(userId);
        if (RuleManager.dianPao(player, it, room.getDealers().getVersatileTiles())) {
            // 用户选择了碰操作  加上同步锁，避免一个人重复点击多次
            if (!room.selectedOperatePlayedCard(userId, OperationOrderEnum.DIAN_PAO)) {
                throw new CustomException("你已经选择了操作，请勿重复操作");
            }
            long startMillis = System.currentTimeMillis();
            // 判断自己是否是操作竞争获胜者
            while (true) {
                // 判断是否所有人都已经结束选择，如果结束选择，则结束循环，继续下面的操作
                //  || System.currentTimeMillis() - startMillis >= WAITING_FOR_MILLIS
                if (room.hasFinishOperatePlayedCard()) {
                    break;
                }
            }
            // 如果自己是胜者，继续下面的操作, 否则直接返回
            if (!room.isWinnerOfOperate(userId)) {
                return;
            }
            // 加入牌组
            player.grabbing(it);
            // 推送消息到各个用户，包括每个用户的牌，以及谁胡牌了
            handleAfterHu(userId, room, true);
        } else {
            throw new CustomException("你不能点炮");
        }
    }

    /**
     * 自摸
     * @param userId
     */
    @SneakyThrows
    public static synchronized void ziMo(String userId) {
        // 获取房间
        Room room = RoomManager.getUserRoom(userId);
        if (!room.isCurPlayer(userId)) {
            throw new CustomException("还没轮到你");
        }
        // 获取用户信息
        Player player = room.getPlayer(userId);
        if (RuleManager.canZiMo(player, room.getDealers().getVersatileTiles())) {
            room.initStatus();
            // 推送消息到各个用户，包括每个用户的牌，以及谁胡牌了
            handleAfterHu(userId, room, false);
        } else {
            throw new CustomException("你不能自摸");
        }
    }

    /**
     * 没有剩余手牌之后做的事
     */
    public static synchronized void handleAfterNoRemainingTiles(Room room) {
        room.initStatus();
        room.clearLastTile();
        room.clearOperatePlayedCardMap();
        List<Player> allPlayers = room.getAllPlayers();
        // 所有玩家当前拥有的手牌
        Map<String, List<TilesEnum>> allPlayersOwnedTiles = new HashMap<>();
        // 所有玩家暗杠的牌
        Map<String, List<TilesGroup>> allPlayersGroups = new HashMap<>();
        allPlayers.stream().forEach(_player -> {
            String uid = _player.getUserId();
            allPlayersOwnedTiles.put(uid, _player.getOwnedTiles());
            List<TilesEnum> anGangTiles = new ArrayList<>();
            _player.getOwnedTilesGroup().stream().forEach(group -> {
                if (group.getType() == TilesGroupTypeEnum.AN_GANG) {
                    anGangTiles.add(group.getTiles().get(0));
                }
            });
            allPlayersGroups.put(uid, _player.getOwnedTilesGroup());
        });
        // 给每个用户推送
        allPlayers.stream().forEach(_player -> {
            OutputNoRemainingTilesMsgData msgData = new OutputNoRemainingTilesMsgData();
            String uid = _player.getUserId();
            // 移除自己的手牌，只保留其他人的牌
            Map<String,List<TilesEnum>> othersOwnedTiles = new HashMap<>(allPlayersOwnedTiles);
            othersOwnedTiles.remove(uid);
            Map<String,List<TilesGroup>> othersGroups = new HashMap<>(allPlayersGroups);
            othersGroups.remove(uid);
            // 将所有暗杠变成明杠，方便展示
            for (String key : othersGroups.keySet()) {
                othersGroups.get(key).stream().forEach(group -> {
                    if (group.getType() == TilesGroupTypeEnum.AN_GANG) {
                        group.setType(TilesGroupTypeEnum.MING_GANG);
                    }
                });
            }
            msgData.setOthersOwnedTiles(othersOwnedTiles);
            msgData.setOthersGroups(othersGroups);
            // 推送给用户
            MessageHandler.sendToUser(uid, new OutputMessage(OutputMsgTypeEnum.HU.getCode(), msgData));
        });
    }

    /**
     * 有用户胡牌之后做的事情
     * @param userId
     */
    public static synchronized void handleAfterHu(String userId, Room room, boolean isDianPao) {
        room.initStatus();
        room.clearLastTile();
        room.clearOperatePlayedCardMap();
        List<Player> allPlayers = room.getAllPlayers();
        // 所有玩家当前拥有的手牌
        Map<String, List<TilesEnum>> allPlayersOwnedTiles = new HashMap<>();
        // 所有玩家暗杠的牌
        Map<String, List<TilesGroup>> allPlayersGroups = new HashMap<>();
        if (isDianPao) {
            // 移除最近一张用户打过的手牌
            Player playedPlayer = room.getPlayer(room.getPreviousUserId());
            playedPlayer.removeRecentPlayedTiles();
        }
        allPlayers.stream().forEach(_player -> {
            String uid = _player.getUserId();
            allPlayersOwnedTiles.put(uid, _player.getOwnedTiles());
            List<TilesEnum> anGangTiles = new ArrayList<>();
            _player.getOwnedTilesGroup().stream().forEach(group -> {
                if (group.getType() == TilesGroupTypeEnum.AN_GANG) {
                    anGangTiles.add(group.getTiles().get(0));
                }
            });
            allPlayersGroups.put(uid, _player.getOwnedTilesGroup());
        });
        // 给每个用户推送
        allPlayers.stream().forEach(_player -> {
            OutputHuMsgData msgData = new OutputHuMsgData();
            msgData.setUserId(userId);
            msgData.setDianPao(isDianPao);
            if (isDianPao) {
                msgData.setPlayedUserId(room.getPreviousUserId());
            }
            String uid = _player.getUserId();
            // 移除自己的手牌，只保留其他人的牌
            Map<String,List<TilesEnum>> othersOwnedTiles = new HashMap<>(allPlayersOwnedTiles);
            othersOwnedTiles.remove(uid);
            Map<String,List<TilesGroup>> othersGroups = new HashMap<>(allPlayersGroups);
            othersGroups.remove(uid);
            // 将所有暗杠变成明杠，方便展示
            for (String key : othersGroups.keySet()) {
                othersGroups.get(key).stream().forEach(group -> {
                    if (group.getType() == TilesGroupTypeEnum.AN_GANG) {
                        group.setType(TilesGroupTypeEnum.MING_GANG);
                    }
                });
            }
            msgData.setOthersOwnedTiles(othersOwnedTiles);
            msgData.setOthersGroups(othersGroups);
            // 推送给用户
            MessageHandler.sendToUser(uid, new OutputMessage(OutputMsgTypeEnum.HU.getCode(), msgData));
        });
    }

    /**
     * 玩家离开
     * @param userId
     */
    @SneakyThrows
    public static synchronized void leave(String userId) {
        Room room = RoomManager.getUserRoom(userId);
        if (room == null) {
            throw new CustomException("未加入任何房间");
        }
        room.leave(userId);
        RoomManager.removeFromRoom(userId);
        OutputLeaveRoomMsgData msgData = new OutputLeaveRoomMsgData(userId);
        List<String> allUserIds = room.getAllUserIds();
        // 给其他用户推送该用户离开的消息
        MessageHandler.sendToUsers(allUserIds, new OutputMessage(OutputMsgTypeEnum.LEAVE_ROOM.getCode(), msgData));
    }
}
